package com.xw.unification.utils.http.client;

import com.xw.unification.helper.StringHelper;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.http.HttpMethod;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class HttpRequestBuilder {

    protected final HttpRequestBase request;

    protected final CloseableHttpClient httpClient;

    private Map<String, List<String>> params;

    private boolean needUriEncoding;

    private Map<String, List<String>> headerParams;

    HttpRequestBuilder(HttpRequestBase request, CloseableHttpClient httpClient) {
        this.request = request;
        this.httpClient = httpClient;
    }

    public HttpRequestBase parseRequest(boolean needUriEncoding) {
        return parseRequest(needUriEncoding, null);
    }

    public HttpRequestBase parseRequest(boolean needUriEncoding, RequestConfig requestConfig) {
        HttpRequestBase requestClone;
        HttpMethod method = HttpMethod.resolve(request.getMethod());
        URI uri = request.getURI();
        if (method == null) {
            throw new IllegalArgumentException("Unknown request method.");
        }
        switch (method) {
            case GET:
                requestClone = new HttpGet(uri);
                break;
            case DELETE:
                requestClone = new HttpDelete(uri);
                break;
            case POST:
                requestClone = new HttpDelete(uri);
                break;
            case PUT:
                requestClone = new HttpPut(uri);
                break;
            case PATCH:
                requestClone = new HttpPatch(uri);
                break;
            case OPTIONS:
                requestClone = new HttpOptions(uri);
                break;
            default:
                throw new IllegalArgumentException("Unknown request method.");
        }
        if (requestConfig != null) {
            requestClone.setConfig(requestConfig);
        }
        doParseRequest(requestClone);
        return requestClone;
    }

    public CloseableHttpResponse request() {
        try {
            doParseRequest(request);
            return httpClient.execute(request);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public HttpRequestBuilder header(Map<String, List<String>> headerParams) {
        this.headerParams = headerParams;
        return this;
    }

    public HttpRequestBuilder query(Map<String, List<String>> params) {
        this.params = params;
        return this;
    }

    public HttpRequestBuilder query(Map<String, List<String>> params, boolean needUriEncoding) {
        this.params = params;
        this.needUriEncoding = needUriEncoding;
        return this;
    }

    protected void parseHeaderParams(HttpRequestBase request) {
        if (request == null || headerParams == null || headerParams.isEmpty()) {
            return;
        }
        // add header params to request
        for (String key : headerParams.keySet()) {
            if (key == null) {
                continue;
            }
            List<String> valueList = headerParams.get(key);
            for (String value : valueList) {
                if (value != null) {
                    request.setHeader(key, value);
                }
            }
        }
    }

    protected void parseQueryParams(HttpRequestBase request, boolean encoded, String encodingName) {
        if (request == null) {
            return;
        }
        URI uri = request.getURI();
        if (uri == null) {
            return;
        }
        LinkedHashMap<String, Set<String>> queryParams = StringHelper.parseQueryParams(uri, true);
        Set<String> valueSet;
        if (params != null && !params.isEmpty()) {
            for (String key : params.keySet()) {
                valueSet = queryParams.getOrDefault(key, new LinkedHashSet<>());
                valueSet.addAll(params.get(key));
                queryParams.put(key, valueSet);
            }
        }
        StringBuilder builder = new StringBuilder();
        try {
            for (String key : queryParams.keySet()) {
                for (String value : queryParams.get(key)) {
                    builder.append(key);
                    builder.append('=');
                    builder.append(encoded ? URLEncoder.encode(value, encodingName) : value);
                    builder.append('&');
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("URI encoding is unsupported", e);
        }
        // add query params to uri
        if (builder.length() > 0 && builder.charAt(builder.length() - 1) == '&') {
            builder.deleteCharAt(builder.length() - 1);
        }
        try {
            uri = new URI(uri.getScheme(), uri.getRawUserInfo(), uri.getHost(), uri.getPort(), uri.getRawPath(),
                    "", uri.getRawFragment());
        } catch (URISyntaxException e) {
            throw new RuntimeException("URI syntax is invalid", e);
        }
        builder.insert(0, uri.toString());
        request.setURI(URI.create(builder.toString()));
    }

    protected void parseBeforeRequest(HttpRequestBase request) {
        // do nothing for no-body request
    }

    /**
     * 实际解析 request 的方法
     * @param request
     */
    private void doParseRequest(HttpRequestBase request) {
        parseHeaderParams(request);
        Header contentType = request.getFirstHeader("Content-Type");
        String encodingName;
        if (contentType == null || !StringHelper.hasText(contentType.getValue())) {
            encodingName = StandardCharsets.UTF_8.name();
        } else {
            encodingName = contentType.getValue();
        }
        parseQueryParams(request, needUriEncoding, encodingName);
        parseBeforeRequest(request);
    }

}

